// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

// Package les implements the Light Ethereum Subprotocol.
package light

import (
	"crypto/ecdsa"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/palletone/go-palletone/common"
	"github.com/palletone/go-palletone/common/crypto"
	"github.com/palletone/go-palletone/common/log"
	"github.com/palletone/go-palletone/dag/modules"
	"github.com/palletone/go-palletone/light/les"
)

// Constants to match up protocol versions and messages
const (
	lpv1 = 1
)

// Supported versions of the les protocol (first is primary)
var (
	ClientProtocolVersions = []uint{lpv1}
	ServerProtocolVersions = []uint{lpv1}
)

// Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = map[uint]uint64{lpv1: 15}

const (
	NetworkId          = 1
	ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
)

// les protocol message codes
const (
	// Protocol messages belonging to LPV1
	StatusMsg          = 0x00
	AnnounceMsg        = 0x01
	GetBlockHeadersMsg = 0x02
	BlockHeadersMsg    = 0x03
	GetUTXOsMsg        = 0x04
	UTXOsMsg           = 0x05
	GetProofsMsg       = 0x06
	ProofsMsg          = 0x07
	SendTxMsg          = 0x08
	GetLeafNodesMsg    = 0x09
	LeafNodesMsg       = 0x0a
	// Protocol messages belonging to LPV2
	//GetProofsV2Msg         = 0x0f
	//ProofsV2Msg            = 0x10
	//GetHelperTrieProofsMsg = 0x11
	//HelperTrieProofsMsg    = 0x12
	//SendTxV2Msg            = 0x13
	//GetTxStatusMsg         = 0x14
	//TxStatusMsg            = 0x15
)

type errCode int

const (
	ErrMsgTooLarge = iota
	ErrDecode
	ErrInvalidMsgCode
	ErrProtocolVersionMismatch
	ErrNetworkIdMismatch
	ErrGenesisBlockMismatch
	ErrNoStatusMsg
	ErrExtraStatusMsg
	ErrSuspendedPeer
	ErrUselessPeer
	ErrRequestRejected
	ErrUnexpectedResponse
	ErrInvalidResponse
	ErrTooManyTimeouts
	ErrMissingKey
)

func (e errCode) String() string {
	return errorToString[int(e)]
}

// XXX change once legacy code is out
var errorToString = map[int]string{
	ErrMsgTooLarge:             "Message too long",
	ErrDecode:                  "Invalid message",
	ErrInvalidMsgCode:          "Invalid message code",
	ErrProtocolVersionMismatch: "Protocol version mismatch",
	ErrNetworkIdMismatch:       "NetworkId mismatch",
	ErrGenesisBlockMismatch:    "Genesis block mismatch",
	ErrNoStatusMsg:             "No status message",
	ErrExtraStatusMsg:          "Extra status message",
	ErrSuspendedPeer:           "Suspended peer",
	ErrRequestRejected:         "Request rejected",
	ErrUnexpectedResponse:      "Unexpected response",
	ErrInvalidResponse:         "Invalid response",
	ErrTooManyTimeouts:         "Too many request timeouts",
	ErrMissingKey:              "Key missing from list",
}

type announceBlock struct {
	Hash   common.Hash // Hash of one particular block being announced
	Number uint64      // Number of one particular block being announced
	//Td     *big.Int    // Total difficulty of one particular block being announced
}

// announceData is the network packet for the block announcements.
type announceData struct {
	Hash   common.Hash        // Hash of one particular block being announced
	Number modules.ChainIndex // Number of one particular block being announced
	Header modules.Header
	Update keyValueList
}

// sign adds a signature to the block announcement by the given privKey
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
	rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number.Index /*, a.Td*/})
	sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey)
	a.Update = a.Update.add("sign", sig)
}

// checkSignature verifies if the block announcement has a valid signature by the given pubKey
func (a *announceData) checkSignature(pubKey *ecdsa.PublicKey) error {
	return nil
	//var sig []byte
	//if err := a.Update.decode().get("sign", &sig); err != nil {
	//	return err
	//}
	//rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number})
	//recPubkey, err := secp256k1.RecoverPubkey(crypto.Keccak256(rlp), sig)
	//if err != nil {
	//	return err
	//}
	//pbytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
	//if bytes.Equal(pbytes, recPubkey) {
	//	return nil
	//} else {
	//	return errors.New("Wrong signature")
	//}
}

//type blockInfo struct {
//	Hash   common.Hash         // Hash of one particular block being announced
//	Number *modules.ChainIndex // Number of one particular block being announced
//	//Td     *big.Int            // Total difficulty of one particular block being announced
//}

// getBlockHeadersData represents a block header query.
type getBlockHeadersData struct {
	Origin  hashOrNumber // Block from which to retrieve headers
	Amount  uint64       // Maximum number of headers to retrieve
	Skip    uint64       // Blocks to skip between consecutive headers
	Reverse bool         // Query direction (false = rising towards latest, true = falling towards genesis)
}

// hashOrNumber is a combined field for specifying an origin block.
type hashOrNumber struct {
	Hash   common.Hash        // Block hash from which to retrieve headers (excludes Number)
	Number modules.ChainIndex // Block hash from which to retrieve headers (excludes Hash)
}

// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
// two contained union fields.
//func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
//	if hn.Hash == (common.Hash{}) {
//		return rlp.Encode(w, hn.Number)
//	}
//	if hn.Number != 0 {
//		return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
//	}
//	return rlp.Encode(w, hn.Hash)
//}
//
//// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
//// into either a block hash or a block number.
//func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
//	_, size, _ := s.Kind()
//	origin, err := s.Raw()
//	if err == nil {
//		switch {
//		case size == 32:
//			err = rlp.DecodeBytes(origin, &hn.Hash)
//		case size <= 8:
//			err = rlp.DecodeBytes(origin, &hn.Number)
//		default:
//			err = fmt.Errorf("invalid input size %d for origin", size)
//		}
//	}
//	return err
//}

// CodeData is the network response packet for a node data retrieval.
type CodeData []struct {
	Value []byte
}

type proofsRespData struct {
	txhash     common.Hash
	headerhash common.Hash //header hash
	txroothash common.Hash
	key        []byte       //tx index
	pathData   les.NodeList //txs path
	index      string
}

//type txStatus struct {
//	Status core.TxStatus
//	Lookup *core.TxLookupEntry `rlp:"nil"`
//	Error  string
//}

func (p *proofsRespData) encode() ([][]byte, error) {
	resp := [][]byte{}
	resp = append(resp, p.txhash.Bytes())
	resp = append(resp, p.headerhash.Bytes())
	resp = append(resp, p.txroothash.Bytes())
	resp = append(resp, p.key)
	path, err := rlp.EncodeToBytes(p.pathData)
	if err != nil {
		log.Debug("proofsRespData encode", "err", err, "pathdata", p.pathData)
		return nil, err
	}
	resp = append(resp, path)
	resp = append(resp, []byte(p.index))
	return resp, nil
}

func (p *proofsRespData) decode(data [][]byte) error {

	p.txhash.SetBytes(data[0])
	p.headerhash.SetBytes(data[1])
	p.txroothash.SetBytes(data[2])
	p.key = data[3]

	if err := rlp.DecodeBytes(data[4], &p.pathData); err != nil {
		log.Debug("proofsRespData decode rlp.DecodeBytes pathData", "err", err, "data", data[4])
		return err
	}
	p.index = string(data[5])
	return nil
}
